package class

import (
	"gitee.com/microwww/jvm-go/jvm/classfile"
)

type Constant interface{}

type ConstantPool struct {
	class       *Class // 当前 class 文件对应的类
	consts      []Constant
	classLoader *ClassLoader
}

type ref struct {
	cp        *ConstantPool // 当前 class 文件的变量池
	className string        // 文件的 `class` 的代码调用 使用其他的类的名字
	class     *Class        // `className` 对应的类
}

func (r *ref) Class() *Class {
	return r.class
}

func (r *ref) ResolveClass() *Class {
	if r.class == nil {
		r.resolveClass()
	}
	return r.class
}

func (r *ref) resolveClass() {
	r.class = r.cp.classLoader.DefineClass(r.className)
}

type ClassRef struct {
	ref
}

type MemberRef struct {
	ref
	name       string
	descriptor string
}

type FieldRef struct {
	MemberRef
	field *Field
}

func (r *FieldRef) ResolveField() *Field {
	if r.field == nil {
		r.resolveField()
	}
	return r.field
}

func (r *FieldRef) resolveField() {
	r.resolveClass()
	fd := r.lookupFiled(r.class, r.name, r.descriptor)
	if fd == nil {
		panic("java.lang.NoSuchFieldError")
	}
	c := r.cp.class
	if !fd.isAccessibleTo(c) {
		panic("java.lang.IllegalAccessError")
	}
	r.field = fd
}

func (r *FieldRef) lookupFiled(class *Class, name, descriptor string) *Field {
	for _, f := range class.fields {
		if f.name == name && f.descriptor == descriptor {
			return f
		}
	}
	if class.superClass != nil {
		return r.lookupFiled(class.superClass, name, descriptor)
	}
	return nil
}

type MethodRef struct {
	MemberRef
	method *Method
}

func (r *MethodRef) ResolveMethod() *Method {
	if r.method == nil {
		r.resolveMethod()
	}
	return r.method
}

func (r *MethodRef) resolveMethod() *Method {
	c := r.ResolveClass()
	mth := lookupMethod(c, r.name, r.descriptor)
	if mth == nil {
		panic("java.lang.NoSuchMethodError")
	}
	r.method = mth
	return mth
}

func lookupMethod(clazz *Class, name, descriptor string) *Method {
	if clazz.IsInterface() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	mth := LookupMethodInClass(clazz, name, descriptor)
	if mth == nil {
		//  TODO:: 不查找父类实现的接口??
		mth = lookupMethodInInterfaces(clazz.interfaces, name, descriptor)
	}
	return mth
}

func LookupMethodInClass(clazz *Class, name, descriptor string) *Method {
	for ch := clazz; ch != nil; ch = clazz.superClass {
		for _, mth := range ch.Methods() {
			// log.Default().Printf("methods %v  %v \n", i, mth.name)
			if mth.name == name && mth.descriptor == descriptor {
				return mth
			}
		}
	}
	return nil
}

// lookupMethodInInterfaces 不判断是否接口, 所以使用私有方法
func lookupMethodInInterfaces(faces []*Class, name, descriptor string) *Method {
	for _, fc := range faces {
		// LookupMethodInClass(fc, name, desc)
		for _, mth := range fc.Methods() {
			if mth.name == name && mth.descriptor == descriptor {
				return mth
			}
		}
		mth := lookupMethodInInterfaces(fc.interfaces, name, descriptor)
		if mth != nil {
			return mth
		}
	}
	return nil
}

type InterfaceMethodRef struct {
	MemberRef
	method *Method
}

func (r *InterfaceMethodRef) ResolvedInterfaceMethod() *Method {
	if r.method == nil {
		r.resolveMethod()
	}
	return r.method
}

func (r *InterfaceMethodRef) resolveMethod() *Method {
	c := r.ResolveClass()
	mth := lookupMethodInInterfaces([]*Class{c}, r.name, r.descriptor)
	if mth == nil {
		panic("java.lang.NoSuchMethodError")
	}
	r.method = mth
	return mth
}

func (c *ConstantPool) GetConstant(index uint) Constant {
	return c.consts[index]
}

func (c *ConstantPool) loadConstantPool(cf classfile.ConstantPool) {
	ct := make([]Constant, len(cf))
	for i, info := range cf {
		switch info.(type) {
		case *classfile.CONSTANT_Class_info:
			name := info.(*classfile.CONSTANT_Class_info).Name()
			ct[i] = &ClassRef{
				ref: ref{
					cp:        c,
					className: name,
				},
			}
		case *classfile.CONSTANT_Fieldref_info:
			fr := info.(*classfile.CONSTANT_Fieldref_info)
			classname := fr.ClassName()          // java.lang.System
			name, descriptor := fr.NameAndType() // out, Ljava/io/PrintStream;

			ct[i] = &FieldRef{
				MemberRef: MemberRef{
					ref: ref{
						cp:        c,
						className: classname,
					},
					name:       name,
					descriptor: descriptor,
				},
				// field: &Field{}, // load field to set
			}
		case *classfile.CONSTANT_Methodref_info:
			fr := info.(*classfile.CONSTANT_Methodref_info)
			classname := fr.ClassName()          // java.lang.System
			name, descriptor := fr.NameAndType() // out, Ljava/io/PrintStream;

			ct[i] = &MethodRef{
				MemberRef: MemberRef{
					ref: ref{
						cp:        c,
						className: classname,
					},
					name:       name,
					descriptor: descriptor,
				},
				// field: &Field{}, // load field to set
			}
		case *classfile.CONSTANT_InterfaceMethodref_info:
			fr := info.(*classfile.CONSTANT_InterfaceMethodref_info)
			classname := fr.ClassName()          // java.lang.System
			name, descriptor := fr.NameAndType() // out, Ljava/io/PrintStream;

			ct[i] = &InterfaceMethodRef{
				MemberRef: MemberRef{
					ref: ref{
						cp:        c,
						className: classname,
					},
					name:       name,
					descriptor: descriptor,
				},
				// field: &Field{}, // load field to set
			}
		case *classfile.CONSTANT_Integer_info:
			ct[i] = info.(*classfile.CONSTANT_Integer_info).Value()
		case *classfile.CONSTANT_Long_info:
			ct[i] = info.(*classfile.CONSTANT_Long_info).Value()
			i++
		case *classfile.CONSTANT_Float_info:
			ct[i] = info.(*classfile.CONSTANT_Float_info).Value()
		case *classfile.CONSTANT_Double_info:
			ct[i] = info.(*classfile.CONSTANT_Double_info).Value()
			i++
		case *classfile.CONSTANT_String_info:
			ct[i] = info.(*classfile.CONSTANT_String_info).StringValue()
		default:
			// TODO :: log.Default().Printf("Skip unneeded type: %T", typ)
		}
	}
	c.consts = ct
}
